package com.codeman.crawl.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.codeman.business.book.domain.Book;
import com.codeman.business.book.domain.BookContent;
import com.codeman.business.book.domain.BookIndex;
import com.codeman.business.crawl.domain.CrawlSingleTask;
import com.codeman.business.crawl.domain.CrawlSource;
import com.codeman.business.crawl.service.ICrawlSingleTaskService;
import com.codeman.business.crawl.service.ICrawlSourceService;
import com.codeman.common.constant.Constants;
import com.codeman.common.core.redis.RedisCache;
import com.codeman.common.exception.CustomException;
import com.codeman.common.utils.IdWorker;
import com.codeman.common.utils.ThreadUtils;
import com.codeman.common.utils.spring.SpringUtils;
import com.codeman.crawl.core.crawl.CrawlParser;
import com.codeman.crawl.core.crawl.RuleBean;
import com.codeman.crawl.service.ICrawlBookService;
import com.codeman.crawl.service.ICrawlService;

import com.codeman.crawl.utils.HttpUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * @author Administrator
 */
@Service
@Slf4j
public class CrawlServiceImpl implements ICrawlService {
    @Autowired
    private ICrawlSourceService crawlSourceService;
    @Autowired
    private ICrawlSingleTaskService crawlSingleTaskService;
    @Autowired
    private ICrawlBookService crawlBookService;
    @Autowired
    private RedisCache redisCache;


    @Override
    public void addCrawlSource(CrawlSource source) {
        Date currentDate = new Date();
        source.setCreateTime(currentDate);
        source.setUpdateTime(currentDate);
        crawlSourceService.save(source);

    }

    @Override
    public List<CrawlSource> listCrawlByPage(int pageNo, int pageSize) {
        Page<CrawlSource> page = new Page<>(pageNo, pageSize);
        QueryWrapper<CrawlSource> queryWrapper = new QueryWrapper<>();
        queryWrapper.orderByAsc("update_time");
        IPage<CrawlSource> list = crawlSourceService.page(page, queryWrapper);
        return list.getRecords();
    }

    @SneakyThrows
    @Override
    public void openOrCloseCrawl(Integer sourceId, String sourceStatus) {

        //判断是开启还是关闭，如果是关闭，则修改数据库状态后获取该爬虫正在运行的线程集合并全部停止
        //如果是开启，先查询数据库中状态，判断该爬虫源是否还在运行，如果在运行，则忽略，
        // 如果没有则修改数据库状态，并启动线程爬取小说数据加入到runningCrawlThread中
        if ("0".equals(sourceStatus)) {
            //关闭,直接修改数据库状态，并直接修改数据库状态后获取该爬虫正在运行的线程集合全部停止
            SpringUtils.getBean(ICrawlService.class).updateCrawlSourceStatus(sourceId, sourceStatus);
            Set<Long> runningCrawlThreadId = redisCache.getCacheObject(Constants.RUNNING_CRAWL_THREAD_KEY_PREFIX + sourceId);
            if (runningCrawlThreadId != null) {
                for (Long ThreadId : runningCrawlThreadId) {
                    Thread thread = ThreadUtils.findThread(ThreadId);
                    if (thread != null && thread.isAlive()) {
                        thread.interrupt();
                    }
                }
            }


        } else {
            //开启
            //查询爬虫源状态和规则
            CrawlSource source = queryCrawlSource(sourceId);
            String realSourceStatus = source.getSourceStatus();

            if ("0".equals(realSourceStatus)) {
                //该爬虫源已经停止运行了,修改数据库状态，并启动线程爬取小说数据加入到runningCrawlThread中
                SpringUtils.getBean(ICrawlService.class).updateCrawlSourceStatus(sourceId, sourceStatus);
                RuleBean ruleBean = new ObjectMapper().readValue(source.getCrawlRule(), RuleBean.class);

                Set<Long> threadIds = new HashSet<>();
                //按分类开始爬虫解析任务
                for (int i = 1; i < 8; i++) {
                    final int catId = i;
                    Thread thread = new Thread(() -> {

                        parseBookList(catId, ruleBean, sourceId);

                    });
                    thread.start();
                    //thread加入到监控缓存中
                    threadIds.add(thread.getId());

                }
                redisCache.setCacheObject(Constants.RUNNING_CRAWL_THREAD_KEY_PREFIX + sourceId, threadIds);
            }


        }

    }

    @Override
    public CrawlSource queryCrawlSource(Integer sourceId) {
        return crawlSourceService.getById(sourceId);
    }

    @Override
    public void addCrawlSingleTask(CrawlSingleTask singleTask) {

        if (crawlBookService.queryIsExistByBookNameAndAuthorName(singleTask.getBookName(), singleTask.getAuthorName())) {
            throw new CustomException("该小说已存在");

        }
        singleTask.setCreateTime(new Date());
        crawlSingleTaskService.save(singleTask);
    }

    @Override
    public List<CrawlSingleTask> listCrawlSingleTaskByPage(int pageNo, int pageSize) {
        Page<CrawlSingleTask> page = new Page<>(pageNo, pageSize);
        QueryWrapper<CrawlSingleTask> queryWrapper = new QueryWrapper<>();
        queryWrapper.orderByDesc("create_time");
        IPage<CrawlSingleTask> list = crawlSingleTaskService.page(page, queryWrapper);
        return list.getRecords();
    }

    @Override
    public void delCrawlSingleTask(int id) {
        crawlSingleTaskService.removeById(id);
    }

    @Override
    public CrawlSingleTask getCrawlSingleTask() {
        String sql = "limit 1";
        QueryWrapper<CrawlSingleTask> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("task_status", "2")
                .orderByAsc("create_time")
                .last(sql);
        List<CrawlSingleTask> list = crawlSingleTaskService.list(queryWrapper);
        return list.size() > 0 ? list.get(0) : null;
    }

    @Override
    public void updateCrawlSingleTask(CrawlSingleTask task, String status) {
        byte excCount = task.getExcCount();
        excCount += 1;
        task.setExcCount(excCount);
        if ("1".equals(status) || excCount == 5) {
            //当采集成功或者采集次数等于5，则更新采集最终状态，并停止采集
            task.setTaskStatus(status);
        }
        crawlSingleTaskService.updateById(task);

    }

    /**
     * 解析分类列表
     */

    @Override
    public void parseBookList(int catId, RuleBean ruleBean, Integer sourceId) {

        //当前页码1
        int page = 1;
        int totalPage = page;

        while (page <= totalPage) {

            try {

                if (StringUtils.isNotBlank(ruleBean.getCatIdRule().get("catId" + catId))) {
                    //拼接分类URL
                    String catBookListUrl = ruleBean.getBookListUrl()
                            .replace("{catId}", ruleBean.getCatIdRule().get("catId" + catId))
                            .replace("{page}", page + "");

                    String bookListHtml = HttpUtil.getByHttpClientWithChrome(catBookListUrl);
                    if (bookListHtml != null) {
                        Pattern bookIdPatten = Pattern.compile(ruleBean.getBookIdPatten());
                        Matcher bookIdMatcher = bookIdPatten.matcher(bookListHtml);
                        boolean isFindBookId = bookIdMatcher.find();
                        while (isFindBookId) {
                            try {
                                //1.阻塞过程（使用了 sleep,同步锁的 wait,socket 中的 receiver,accept 等方法时）
                                //捕获中断异常InterruptedException来退出线程。
                                //2.非阻塞过程中通过判断中断标志来退出线程。
                                if (Thread.currentThread().isInterrupted()) {
                                    return;
                                }


                                String bookId = bookIdMatcher.group(1);
                                parseBookAndSave(catId, ruleBean, sourceId, bookId);
                            } catch (Exception e) {
                                log.error(e.getMessage(), e);
                            }


                            isFindBookId = bookIdMatcher.find();
                        }

                        Pattern totalPagePatten = Pattern.compile(ruleBean.getTotalPagePatten());
                        Matcher totalPageMatcher = totalPagePatten.matcher(bookListHtml);
                        boolean isFindTotalPage = totalPageMatcher.find();
                        if (isFindTotalPage) {

                            totalPage = Integer.parseInt(totalPageMatcher.group(1));

                        }


                    }
                }
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }

            page += 1;
        }


    }

    @Override
    public boolean parseBookAndSave(int catId, RuleBean ruleBean, Integer sourceId, String bookId) {
        Book book = CrawlParser.parseBook(ruleBean, bookId);
        if (book.getBookName() == null || book.getAuthorName() == null) {
            return false;
        }
        //这里只做新书入库，查询是否存在这本书
        Book existBook = crawlBookService.queryBookByBookNameAndAuthorName(book.getBookName(), book.getAuthorName());
        //如果该小说不存在，则可以解析入库，但是标记该小说正在入库，30分钟之后才允许再次入库
        if (existBook == null) {
            //没有该书，可以入库
            book.setCatId(catId);
            //根据分类ID查询分类//说实话我甚至觉得这个字段都没有必要，这是为了啥，适当的冗余吗
            book.setCatName(crawlBookService.queryCatNameByCatId(catId));
            if (catId == 7) {
                //女频
                book.setWorkDirection("1");
            } else {
                //男频
                book.setWorkDirection("0");
            }
            book.setCrawlBookId(bookId);
            book.setCrawlSourceId(sourceId);
            book.setCrawlLastTime(new Date());
            book.setId(new IdWorker().nextId());
            //解析章节目录
            Map<Integer, List> indexAndContentList = CrawlParser.parseBookIndexAndContent(bookId, book, ruleBean, new HashMap<>(0));

            crawlBookService.saveBookAndIndexAndContent(book, (List<BookIndex>) indexAndContentList.get(CrawlParser.BOOK_INDEX_LIST_KEY), (List<BookContent>) indexAndContentList.get(CrawlParser.BOOK_CONTENT_LIST_KEY));

        } else {
            //只更新书籍的爬虫相关字段
            crawlBookService.updateCrawlProperties(existBook.getId(), sourceId, bookId);
        }
        return true;
    }

    @Override
    public void updateCrawlSourceStatus(Integer sourceId, String sourceStatus) {
        CrawlSource source = new CrawlSource();
        source.setId(sourceId);
        source.setSourceStatus(sourceStatus);
        crawlSourceService.updateById(source);
    }

    @Override
    public List<CrawlSource> queryCrawlSourceByStatus(String sourceStatus) {
        QueryWrapper<CrawlSource> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("source_status", sourceStatus);
        return crawlSourceService.list(queryWrapper);
    }
}
